package com.clementheliou.cheliou.dal.model.member;

import static org.apache.commons.lang3.StringUtils.join;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.Type;
import org.hibernate.validator.constraints.Email;
import org.joda.time.LocalDate;

import com.clementheliou.cheliou.dal.model.base.BaseEntity;
import com.clementheliou.cheliou.dal.model.member.enumeration.MemberLocale;
import com.clementheliou.cheliou.dal.model.member.enumeration.MemberRole;
import com.clementheliou.cheliou.dal.model.member.exception.MemberRoleConversionException;

/**
 * Entity dedicated to the members data.
 * 
 * @author Clément HELIOU (clement.heliou@gmail.com)
 * @see {@link BaseEntity};
 * @see {@link Entity};
 * @see {@link Serializable};
 * @see {@link Table};
 * @see {@link UniqueConstraint}.
 * @since 1.0
 */
@Entity
@Table(name = "member", uniqueConstraints = @UniqueConstraint(columnNames = "email"))
public class Member extends BaseEntity implements Serializable {

	/**
	 * UID for serialization.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private static final long serialVersionUID = 4650994588506694646L;

	/**
	 * Separator to use during roles conversions.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private static final char ROLES_SEPARATOR = ',';

	/**
	 * Member's password as a MD5 hash.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(nullable = false)
	@NotNull
	private String password;

	/**
	 * Member's name.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(length = 50, nullable = false)
	@NotNull
	@Pattern(message = "general_only_alphabetical_characters_are_accepted", regexp = "(\\p{L}+)")
	@Size(max = 50)
	private String name;

	/**
	 * Member's first name.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(length = 50, nullable = false)
	@NotNull
	@Pattern(message = "general_only_alphabetical_characters_are_accepted", regexp = "(\\p{L}+)")
	@Size(max = 50)
	private String firstName;

	/**
	 * Member's roles.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(length = 255, nullable = false)
	@NotNull
	private String roles;

	/**
	 * Member's email.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(length = 100, nullable = false)
	@Email(message = "email_wrong_format")
	@NotNull
	@Size(max = 100)
	private String email;

	/**
	 * Member's locale (i.e. country and language codes).
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @see {@link MemberLocale}.
	 * @since 1.0
	 */
	private MemberLocale locale;

	/**
	 * Member's subscription date (without time).
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */

	private LocalDate subscriptionDate;

	/*
	 * Getters & setters.
	 */

	@GeneratedValue(generator = "seq_member")
	@Id
	@SequenceGenerator(name = "seq_member", sequenceName = "seq_member")
	public Integer getId() {
		return id;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	@Basic(optional = false)
	@Column(length = 100, nullable = false)
	@Enumerated(EnumType.STRING)
	@NotNull
	public MemberLocale getLocale() {
		return locale;
	}

	public void setLocale(MemberLocale locale) {
		this.locale = locale;
	}

	/**
	 * Gets the user's roles as a list of {@link MemberRole}.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @return the resulting roles list.
	 * @see {@link MemberRole};
	 * @see {@link Transient}.
	 * @since 1.0
	 */
	@Transient
	public List<MemberRole> getRolesList() {
		MemberRole currentRole;
		final List<MemberRole> rolesList = new ArrayList<MemberRole>();
		final String[] rolesCodes = StringUtils.split(this.roles, ROLES_SEPARATOR);

		for (String roleCode : rolesCodes) {
			currentRole = MemberRole.valueOfCode(roleCode);

			if (null == currentRole) {
				throw new MemberRoleConversionException();
			}

			rolesList.add(currentRole);
		}

		return rolesList;
	}

	/**
	 * Sets the user's roles with a list of {@link MemberRole}.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @param roles the roles's list to be set.
	 * @see {@link MemberRole}.
	 * @since 1.0
	 */
	public void setRolesList(List<MemberRole> roles) {
		final String[] rolesCodes = new String[roles.size()];

		for (int i = 0; i < roles.size(); i++) {
			rolesCodes[i] = roles.get(i).getCode();
		}

		this.roles = join(rolesCodes, ROLES_SEPARATOR);
	}

	public String getRoles() {
		return roles;
	}

	public void setRoles(String roles) {
		this.roles = roles;
	}

	@Column(nullable = false)
	@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
	public LocalDate getSubscriptionDate() {
		return subscriptionDate;
	}

	@Transient
	public Date getSubscriptionJdkDate() {
		return subscriptionDate.toDate();
	}

	public void setSubscriptionDate(LocalDate subscriptionDate) {
		this.subscriptionDate = subscriptionDate;
	}

}
